package services

import (
	"context"
	"errors"
	"strings"
	"unicode/utf8"

	"golang.org/x/crypto/bcrypt"

	"github.com/samaa/photosalesplus/backend/internal/models"
	"github.com/samaa/photosalesplus/backend/internal/repositories"
)

var (
	ErrUsernameTaken = errors.New("用户名已被占用")
	ErrUserNotFound  = errors.New("用户不存在")
	ErrPasswordWrong = errors.New("密码错误")
)

type UserService interface {
	Register(ctx context.Context, username, password, displayName, email string) (*models.User, error)
	Authenticate(ctx context.Context, username, password string) (*models.User, error)
	Delete(ctx context.Context, userID uint) error
	Sanitize(user *models.User) ginUser
	FindByID(ctx context.Context, id uint) (*models.User, error)
	FindByUsername(ctx context.Context, username string) (*models.User, error)
	Create(ctx context.Context, user *models.User) error
	UpdateRole(ctx context.Context, id uint, role string) error
	UpdateProfile(ctx context.Context, id uint, displayName *string, avatarURL *string) (*models.User, error)
}

type ginUser struct {
	ID          uint   `json:"id"`
	Username    string `json:"username"`
	DisplayName string `json:"display_name"`
	Email       string `json:"email"`
	AvatarURL   string `json:"avatar_url"`
	Role        string `json:"role"`
}

type userService struct {
	repo repositories.UserRepository
}

func NewUserService(repo repositories.UserRepository) UserService {
	return &userService{repo: repo}
}

func (s *userService) Register(ctx context.Context, username, password, displayName, email string) (*models.User, error) {
	username = strings.TrimSpace(username)
	if username == "" || password == "" {
		return nil, errors.New("用户名和密码不能为空")
	}

	_, err := s.repo.FindByUsername(ctx, username)
	if err == nil {
		return nil, ErrUsernameTaken
	}

	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return nil, err
	}

	user := &models.User{
		Username:     username,
		DisplayName:  displayName,
		Email:        email,
		Role:         "customer",
		PasswordHash: string(hash),
	}

	if err := s.repo.Create(ctx, user); err != nil {
		return nil, err
	}

	return user, nil
}

func (s *userService) Authenticate(ctx context.Context, username, password string) (*models.User, error) {
	user, err := s.repo.FindByUsername(ctx, username)
	if err != nil {
		return nil, ErrUserNotFound
	}
	if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
		return nil, ErrPasswordWrong
	}
	return user, nil
}

func (s *userService) Delete(ctx context.Context, userID uint) error {
	return s.repo.DeleteByID(ctx, userID)
}

func (s *userService) Sanitize(user *models.User) ginUser {
	if user == nil {
		return ginUser{}
	}
	return ginUser{
		ID:          user.ID,
		Username:    user.Username,
		DisplayName: user.DisplayName,
		Email:       user.Email,
		AvatarURL:   user.AvatarURL,
		Role:        user.Role,
	}
}

func (s *userService) FindByID(ctx context.Context, id uint) (*models.User, error) {
	return s.repo.FindByID(ctx, id)
}

func (s *userService) FindByUsername(ctx context.Context, username string) (*models.User, error) {
	return s.repo.FindByUsername(ctx, strings.TrimSpace(username))
}

func (s *userService) Create(ctx context.Context, user *models.User) error {
	if user == nil {
		return errors.New("user is nil")
	}
	user.Username = strings.TrimSpace(user.Username)
	if user.Role == "" {
		user.Role = "customer"
	}
	return s.repo.Create(ctx, user)
}

func (s *userService) UpdateRole(ctx context.Context, id uint, role string) error {
	return s.repo.UpdateRole(ctx, id, role)
}

func (s *userService) UpdateProfile(ctx context.Context, id uint, displayName *string, avatarURL *string) (*models.User, error) {
	var namePtr *string
	if displayName != nil {
		name := strings.TrimSpace(*displayName)
		if name != "" {
			const maxNameLength = 20
			if utf8.RuneCountInString(name) > maxNameLength {
				runes := []rune(name)
				name = string(runes[:maxNameLength])
			}
		}
		namePtr = &name
	}

	var avatarPtr *string
	if avatarURL != nil {
		url := strings.TrimSpace(*avatarURL)
		avatarPtr = &url
	}

	if err := s.repo.UpdateProfile(ctx, id, namePtr, avatarPtr); err != nil {
		return nil, err
	}

	return s.repo.FindByID(ctx, id)
}
